<?php
/**
 * 模型数据校验
 * 
 * @author Leo Ning <leo.ning@like18.com>
 * @abstract 
 * @version 1.0
 * @since 1.0
 * @copyright 2008-2011 LIKE18 INC.
 * @package model
 */
class ModelValidator {
	private static $INS;
	private $scope = null;
	/**
	 * 使用规则$rule对$data进行校验
	 * 
	 * @static
	 * @param array $rule
	 * @param array $data
	 * @param boolean $strict
	 * @return Ambigous <string, multitype:, mixed>
	 */
	public static function validate($rule, $data, $strict = false, $scope = null) {
		if (self::$INS == null) {
			self::$INS = new ModelValidator ();
		}
		return self::$INS->validate_it ( $rule, $data, $strict, $scope );
	}
	public static function getErrorCls($data, $cls = 'error') {
		$error_cls = array ();
		foreach ( $data as $key => $v ) {
			$error_cls [$key] = $cls;
		}
		return $error_cls;
	}
	/**
	 * 使用规则$rule对$data进行校验
	 * @param array $rule
	 * @param array $data
	 * @param boolean $strict
	 * @return Ambigous <string, multitype:, mixed>
	 */
	public function validate_it($rule, $data, $strict = false, $scope = null) {
		if (empty ( $data ) || ! is_array ( $data ) || empty ( $rule ) || ! is_array ( $rule )) {
			trigger_error ( 'The ModelValidator can not deal with the given rule and data,please check it', E_USER_ERROR );
		}
		$this->scope = $scope;
		$error = array ();
		foreach ( $rule as $key => $r ) { //$key为要校验的字段名,$r为校验规则
			$len1 = strlen ( $key );
			$key = trim ( $key, '[]' );
			$skipable = ($len1 - strlen ( $key )) == 2;
			if (! array_key_exists ( $key, $data )) { //验证规则中的$key在$data中未提供
				if ($strict && ! $skipable) { //严格验证，且该字段不可被跳过时
					$error [$key] = sprintf ( __ ( 'field %s is not definded.' ), $key );
				}
			} else {
				$rules = ! is_array ( $r ) ? array ($r => true ) : $r;
				if (is_array ( $rules )) {
					foreach ( $rules as $rl => $exp ) { //每一个校验规则
						if (method_exists ( $this, 'v_' . $rl )) {
							$rst = call_user_func_array ( array ($this, 'v_' . $rl ), array ($data [$key], $exp, $data, $strict ) );
							if ($rst !== true) { //验证失败,$rst为验证失败原因
								$error [$key] = $rst;
								break;
							}
						} else if ($strict) {
							trigger_error ( 'The validate rule ' . $rl . ' is not defined.', E_USER_ERROR );
						}
					}
				} else if ($strict) {
					trigger_error ( 'The validate rule for ' . $key . ' is not a available rule:' . $rules, E_USER_ERROR );
				}
			}
		}
		return empty ( $error ) ? true : $error;
	}
	//必填项目
	private function v_required($value, $exp, $data, $strict) {
		if (! $this->emp ( $value )) {
			return true;
		} else {
			return $this->getmsg ( $exp, __ ( 'This field is required.' ) );
		}
	}
	//相等
	private function v_equalTo($value, $exp, $data, $strict) {
		$exp = is_array ( $exp ) ? $exp : array ($exp );
		$rst = false;
		if (isset ( $data [$exp [0]] )) {
			if ($strict) {
				$rst = $value === $data [$exp [0]];
			} else {
				$rst = $value == $data [$exp [0]];
			}
		}
		if ($rst) {
			return true;
		} else {
			return $this->getmsg ( $exp, __ ( 'Please enter the same value again.' ), 1 );
		}
	}
	//不相等
	private function v_differTo($value, $exp, $data, $strict) {
		$exp = is_array ( $exp ) ? $exp : array ($exp );
		$rst = false;
		if (isset ( $data [$exp [0]] )) {
			if ($strict) {
				$rst = $value !== $data [$exp [0]];
			} else {
				$rst = $value != $data [$exp [0]];
			}
		}
		if ($rst) {
			return true;
		} else {
			return $this->getmsg ( $exp, __ ( 'Please enter the different value.' ), 1 );
		}
	}
	//数值,包括整数与实数
	private function v_num($value, $exp, $data, $strict) {
		if ($this->emp ( $value ) || is_numeric ( $value )) {
			return true;
		} else {
			return $this->getmsg ( $exp, __ ( 'Please enter a valid number.' ) );
		}
	}
	private function v_number($value, $exp, $data, $strict) {
		return $this->v_num ( $value, $exp, $data, $strict );
	}
	//整数
	private function v_digits($value, $exp, $data, $strict) {
		if ($this->emp ( $value ) || preg_match ( '/^\d+$/', $value )) {
			return true;
		} else {
			return $this->getmsg ( $exp, __ ( 'Please enter only digits.' ) );
		}
	}
	//min
	private function v_min($value, $exp, $data, $strict) {
		if ($this->emp ( $value )) {
			return true;
		}
		$exp = is_array ( $exp ) ? $exp : array ($exp );
		$value = floatval ( $value );
		if ($value >= $exp [0]) {
			return true;
		} else {
			return $this->getmsg ( $exp, sprintf ( __ ( 'Please enter a value greater than or equal to %s.' ), $exp [0] ), 1 );
		}
	}
	//max
	private function v_max($value, $exp, $data, $strict) {
		if ($this->emp ( $value )) {
			return true;
		}
		$exp = is_array ( $exp ) ? $exp : array ($exp );
		$value = floatval ( $value );
		if ($value <= $exp [0]) {
			return true;
		} else {
			return $this->getmsg ( $exp, sprintf ( __ ( 'Please enter a value less than or equal to %s.' ), $exp [0] ), 1 );
		}
	}
	//gt 大于
	private function v_gt($value, $exp, $data, $strict) {
		$exp = is_array ( $exp ) ? $exp : array ($exp );
		$rst = false;
		if (isset ( $data [$exp [0]] ) && $value > $data [$exp [0]]) {
			return true;
		} else {
			return $this->getmsg ( $exp, sprintf ( __ ( 'Please enter a value greater than %s.' ), $data [$exp [0]] ), 1 );
		}
	}
	//ge 大于等于
	private function v_ge($value, $exp, $data, $strict) {
		$exp = is_array ( $exp ) ? $exp : array ($exp );
		$rst = false;
		if (isset ( $data [$exp [0]] ) && $value >= $data [$exp [0]]) {
			return true;
		} else {
			return $this->getmsg ( $exp, sprintf ( __ ( 'Please enter a value greater than or equal to %s.' ), $data [$exp [0]] ), 1 );
		}
	}
	//gt 小于
	private function v_lt($value, $exp, $data, $strict) {
		$exp = is_array ( $exp ) ? $exp : array ($exp );
		$rst = false;
		if (isset ( $data [$exp [0]] ) && $value < $data [$exp [0]]) {
			return true;
		} else {
			return $this->getmsg ( $exp, sprintf ( __ ( 'Please enter a value less than %s.' ), $data [$exp [0]] ), 1 );
		}
	}
	//ge 小于等于
	private function v_le($value, $exp, $data, $strict) {
		$exp = is_array ( $exp ) ? $exp : array ($exp );
		$rst = false;
		if (isset ( $data [$exp [0]] ) && $value <= $data [$exp [0]]) {
			return true;
		} else {
			return $this->getmsg ( $exp, sprintf ( __ ( 'Please enter a value less than or equal to %s.' ), $data [$exp [0]] ), 1 );
		}
	}
	//取值范围
	private function v_range($value, $exp, $data, $strict) {
		if ($this->emp ( $value )) {
			return true;
		}
		if (is_array ( $exp ) && count ( $exp ) >= 2) {
			$value = floatval ( $value );
			if ($value >= $exp [0] && $value <= $exp [1]) {
				return true;
			} else {
				return $this->getmsg ( $exp, sprintf ( __ ( 'Please enter a value between %s and %s.' ), $exp [0], $exp [1] ), 2 );
			}
		} elseif ($strict) {
			trigger_error ( 'the exp for range rule is not valid.it must be range:[min,max]' );
		}
	}
	//minlength
	private function v_minlength($value, $exp, $data, $strict) {
		if ($this->emp ( $value )) {
			return true;
		}
		$exp = is_array ( $exp ) ? $exp : array ($exp );
		$value = function_exists ( 'mb_strlen' ) ? mb_strlen ( $value ) : strlen ( $value );
		if ($value >= intval ( $exp [0] )) {
			return true;
		} else {
			return $this->getmsg ( $exp, sprintf ( __ ( 'Please enter at least %s characters.' ), $exp [0] ), 1 );
		}
	}
	//maxlength
	private function v_maxlength($value, $exp, $data, $strict) {
		if ($this->emp ( $value )) {
			return true;
		}
		$exp = is_array ( $exp ) ? $exp : array ($exp );
		$value = function_exists ( 'mb_strlen' ) ? mb_strlen ( $value ) : strlen ( $value );
		if ($value <= intval ( $exp [0] )) {
			return true;
		} else {
			return $this->getmsg ( $exp, sprintf ( __ ( 'Please enter no more than %s characters.' ), $exp [0] ), 1 );
		}
	}
	//rangelength
	private function v_rangelength($value, $exp, $data, $strict) {
		if ($this->emp ( $value )) {
			return true;
		}
		if (is_array ( $exp ) && count ( $exp ) >= 2) {
			$value = function_exists ( 'mb_strlen' ) ? mb_strlen ( $value ) : strlen ( $value );
			if ($value >= intval ( $exp [0] ) && $value <= intval ( $exp [1] )) {
				return true;
			} else {
				return $this->getmsg ( $exp, sprintf ( __ ( 'Please enter a value between %s and %s characters long.' ), $exp [0], $exp [1] ), 2 );
			}
		} elseif ($strict) {
			trigger_error ( 'the exp for rangelength rule is not valid.it must be rangelength:[min,max]' );
		}
	}
	//用户自定义校验函数
	private function v_callback($value, $exp, $data, $strict) {
		if (function_exists ( $exp )) {
			return call_user_func_array ( $exp, array ($value, $data) );
		}
		if (is_callable ( array ($this->scope, $exp ) )) {
			return call_user_func_array ( array ($this->scope, $exp ), array ($value, $data ) );
		}
		if ($strict) {
			trigger_error ( 'the callback function is not defined.' );
		} else {
			return __ ( "Please enter a value with a valid extension." );
		}
	}
	//正则表达式
	private function v_regexp($value, $exp, $data, $strict) {
		if ($this->emp ( $value )) {
			return true;
		}
		$exp = is_array ( $exp ) ? $exp : array ($exp );
		if (@preg_match ( $exp [0], $value )) {
			return true;
		} else {
			return $this->getmsg ( $exp, __ ( 'Please enter a value with a valid extension.' ), 1 );
		}
	}
	//email
	private function v_email($value, $exp, $data, $strict) {
		if ($this->emp ( $value )) {
			return true;
		}
		if (function_exists ( 'filter_var' )) {
			$rst = filter_var ( $value, FILTER_VALIDATE_EMAIL );
		} else {
			$rst = preg_match ( '/^[_a-z0-9\-]+(\.[_a-z0-9\-]+)*@[a-z0-9][a-z0-9\-]+(\.[a-z0-9-]*)*$/', $value );
		}
		return $rst ? true : $this->getmsg ( $exp, __ ( 'Please enter a valid email address.' ) );
	}
	//url
	private function v_url($value, $exp, $data, $strict) {
		if ($this->emp ( $value )) {
			return true;
		}
		if (function_exists ( 'filter_var' )) {
			$rst = filter_var ( $value, FILTER_VALIDATE_URL );
		} else {
			$rst = preg_match ( '/^[a-zA-z]+://[^\s]$/', $value );
		}
		return $rst ? true : $this->getmsg ( $exp, __ ( 'Please enter a valid URL.' ) );
	}
	//date:true
	//date:"-"
	//date:"msg"
	//date:["-","msg"]
	private function v_date($value, $exp, $data, $strict) {
		if ($this->emp ( $value )) {
			return true;
		}
		$exp = is_array ( $exp ) ? $exp : array ($exp );
		$sp = is_string ( $exp [0] ) && strlen ( $exp [0] ) == 1 ? $exp [0] : '-';
		$value = explode ( $sp, $value );
		if (count ( $value ) == 3 && is_int ( $value [2] ) && @checkdate ( $value [1], $value [2], $value [0] )) {
			return true;
		}
		if (count ( $exp ) == 1 && strlen ( $exp [0] ) > 1) {
			$exp [1] = $exp [0];
		}
		return $this->getmsg ( $exp, __ ( 'Please enter a valid date.' ), 1 );
	}
	//datetime:true
	//datetime:"-"
	//datetime:"msg"
	//datetime:["-","msg"]
	private function v_datetime($value, $exp, $data, $strict) {
		if ($this->emp ( $value )) {
			return true;
		}
		$exp = is_array ( $exp ) ? $exp : array ($exp );
		$sp = is_string ( $exp [0] ) && strlen ( $exp [0] ) == 1 ? $exp [0] : '-';
		$times = explode ( ' ', $value );
		$value = explode ( $sp, $times [0] );
		if (count ( $value ) == 3 && isset ( $times [1] ) && @checkdate ( $value [1], $value [2], $value [0] )) {
			$time = explode ( ':', $times [1] );
			if (count ( $time ) == 3 && $time [0] >= 0 && $time [0] < 24 && $time [1] >= 0 && $time [1] < 59 && $time [2] >= 0 && $time [2] < 59) {
				return true;
			}
		}
		if (count ( $exp ) == 1 && strlen ( $exp [0] ) > 1) {
			$exp [1] = $exp [0];
		}
		return $this->getmsg ( $exp, __ ( 'Please enter a valid datetime.' ), 1 );
	}
	//值是否为空,只权限长度判断
	private function emp($value) {
		return strlen ( trim ( $value ) ) == 0;
	}
	//得到错误信息
	private function getmsg($exp, $default = "", $msgidx = 1) {
		if (is_array ( $exp ) && isset ( $exp ['msg'] )) {
			return $exp ['msg'];
		} else if (is_string ( $exp )) {
			return $exp;
		} else if (is_array ( $exp ) && count ( $exp ) > $msgidx) {
			$msg = array_pop ( $exp );
			if (is_string ( $msg )) {
				return $msg;
			}
		}
		return $default;
	}
}